home *** CD-ROM | disk | FTP | other *** search
/ MacHack 1999 / MacHack 1999.toast / The Hacks / IrComm Remote / DVDRemote / Hack / SerDemo.c < prev    next >
Encoding:
C/C++ Source or Header  |  1999-06-25  |  16.2 KB  |  673 lines  |  [TEXT/CWIE]

  1.  
  2. #include    <Types.h>
  3. //#include    <SysEqu.h>
  4. #include    <Resources.h>
  5. #include    <QuickDraw.h>
  6. #include    <Fonts.h>
  7. #include    <Events.h>
  8. #include    <Menus.h>
  9. #include    <Controls.h>
  10. #include    <Dialogs.h>
  11. #include    <Memory.h>
  12. #include    <Files.h>
  13. #include    <Devices.h>
  14. #include    <Serial.h>
  15. #include    <Timer.h>
  16. #include    <OSUtils.h>
  17. #include    <Processes.h>
  18. #include    <Gestalt.h>
  19. #include    <Errors.h>
  20. #include    <string.h>
  21. #include    <Speech.h>
  22.  
  23.  
  24. #define        rMonitor        1000
  25. #define        kSendButton        1
  26. #define        kStopButton        2
  27. #define        kQuitButton        3
  28. #define        kMsgBox            4
  29. #define        kSendSpinner    5
  30. #define        kRcvSpinner        7
  31. #define        kHoldFlag        9
  32. #define        kBreakButton    10
  33.  
  34. #define        rSerDataRsrc    128
  35. #define        rSpinnerIcon    1000
  36. #define        rHeldIcon        128
  37. #define        rNotHeldIcon    129
  38.  
  39. #define        rPortOpenALRT    256
  40. #define        kReset            2
  41.  
  42. #define        kCtlEnable        0
  43. #define        kCtlDisable        255
  44. #define        kSerBufSize        16384
  45. #define        kSerRdSize        64
  46. #define        kSerConfig        baud9600 + noParity + data8 + stop10
  47. #define        kBreakLength    666                // serial break length in milliseconds 
  48.  
  49. #define drvrName 0x12                // offset to driver name in 'DRVR' std. header 
  50.  
  51. #define dOpened  5
  52. #define dRAMBased  6
  53. #define drvrActive  7            // Device Manager DCtlFlag bits 
  54.                 
  55. #define kSerStatus  8
  56. #define kSerClrBrk  11            // Serial Driver csCodes 
  57. #define kSerSetBrk  12
  58. #define kSerHShakeDTR  14
  59. #define breakR0  128                // mask for break bit in SCC RR0 -- See TN #56 
  60. #define breakErr  8                    // mask for break bit in cumErrs -- System 7.0 
  61.  
  62. #define kDVDName "\pApple DVD Player"
  63. #define kCDName "\pAppleCD Audio Player"
  64.  
  65. /*
  66. typedef struct {
  67.     IOParam        fIOParam;
  68.     long        appA5;
  69. } ExtIOParam;
  70.  
  71. typedef struct {
  72.     TMTask        fTMTask;
  73.     long        appA5;
  74. } ExtTMTask;
  75.  
  76. typedef struct {
  77.     unsigned char    readR0;
  78.     unsigned char    deltaBits;
  79.     short            drvrPosting;
  80. } SERDEventMessage;
  81. */
  82.  
  83.  
  84. //Boolean        gHeldOff,
  85. //            gAllDone = false,
  86. //            gShouldSend = false,
  87. //            gReload = false,
  88. //            gKillBreak = false,
  89. //            gBreakReceived = false;
  90.  
  91. Ptr            gpSerBuf, *ghSerBuf,            // serial receive buffer
  92.             gpOutputData, *ghOutputData,    // send buffer
  93.             gpBitBucket, *ghBitBucket;        // receive processing buffer
  94.  
  95. long        gOutputDataSize;
  96. IOParam        *gpSendPB, **ghSendPB;        // for PBWrite calls to send data
  97. short        gSysVersion,
  98.             gOutRefNum, gInRefNum,
  99. //            gSendCount = 0,
  100.             gBitBucketCount = 0;
  101. //char        *panicString = "Help! I'm stuffed! And here's a bunch of characters to prove it!\n\r";
  102. TMTask        gPostEventTask;
  103.  
  104.  
  105.  
  106. short    Initialize (void);
  107. void    CleanUp (void);
  108. Boolean    OpenSERD (void);
  109. void    CloseSERD (void);
  110. void    DoIOStuff (void);
  111. OSErr    SendData (void);
  112. void    CheckSerData (long reqBytes);
  113. void    CheckSerStatus (void);
  114. pascal Boolean NullGrabber (DialogPtr, EventRecord *evt, short *itemHit);
  115. pascal void    SendCompRout (void);
  116. pascal void FlagBreakTimeout (void);
  117. void ProcessBitBucket(void);
  118. void PrimeTMTask(void);
  119. void ProcessString(unsigned char* s);
  120.  
  121. //#pragma parameter PostEventProc(__A0)
  122. pascal void PostEventProc (void);
  123.  
  124. //OSErr    AssertDrvrOpen (Str255 name, short *refNum);
  125.  
  126. //ParmBlkPtr GetParmBlkPtr (void) = 0x2008;        // MOVE.L A0,D0 
  127. //TMTaskPtr GetTMTaskPtr (void) = 0x2009;            // MOVE.L A1,D0 
  128.  
  129. //#pragma parameter __D0 PBControlImmed(__A0)
  130. //pascal OSErr PBControlImmed(ParmBlkPtr paramBlock) = 0xA204;        // _Control ,IMMED 
  131.  
  132. //#pragma parameter __D0 PBStatusImmed(__A0)
  133. //pascal OSErr PBStatusImmed(ParmBlkPtr paramBlock) = 0xA205;            // _Status ,IMMED 
  134.  
  135.  
  136.  
  137. void main (void)
  138. {
  139.     InitGraf(&qd.thePort);
  140.     InitFonts();
  141.     InitWindows();
  142.     InitMenus();
  143.     InitDialogs(NULL);
  144.     InitCursor();
  145.     
  146. //    if (Initialize() == noErr) {
  147.         
  148.         if (OpenSERD()) {    // open the Serial Driver 
  149.             DoIOStuff();
  150.             CloseSERD();    // close the Serial Driver 
  151.         }
  152.         CleanUp();
  153. //    }
  154.     
  155. }
  156.  
  157. //#define kInputDriver "\p.AIn"
  158. //#define kOutputDriver "\p.AOut"
  159. #define kInputDriver "\p.IrIn"
  160. #define kOutputDriver "\p.IrOut"
  161.  
  162.  
  163. Boolean OpenSERD (void)
  164. {
  165.     OSErr    openOutErr, openInErr;
  166.     OSErr    setBufErr, setCfgErr, setHskErr;
  167.     SerShk    hskFlags;
  168. //    long    finalTicks;
  169.     Boolean    takeOverPort = true;
  170. //    Boolean    openAOut, openAIn;
  171.     
  172. /*    openAOut = AssertDrvrOpen(kOutputDriver, &gOutRefNum) == noErr;
  173.     openAIn = AssertDrvrOpen(kInputDriver, &gInRefNum) == noErr;
  174.     if (openAOut || openAIn) {
  175.         if (takeOverPort = CautionAlert(rPortOpenALRT, nil) == kReset) {
  176.             if (openAIn) {
  177.                 KillIO(gInRefNum);
  178.                 CloseDriver(gInRefNum);
  179.             }
  180.             if (openAOut) {
  181.                 KillIO(gOutRefNum);
  182.                 CloseDriver(gOutRefNum);
  183.             }
  184.         }
  185.     }
  186.     
  187.     if (takeOverPort) {
  188. */
  189.         openOutErr = OpenDriver(kOutputDriver, &gOutRefNum);
  190.         openInErr = OpenDriver(kInputDriver, &gInRefNum);
  191.         
  192.         if (openOutErr == noErr && openInErr == noErr) {
  193.                     
  194.             // It's always good to first set a non-default input buffer, if desired. 
  195.             // There is no output buffering, so specify only the input driver.       
  196.             
  197.             setBufErr = SerSetBuf(gInRefNum, gpSerBuf, kSerBufSize);
  198.             
  199.             hskFlags.fXOn = false;
  200.             hskFlags.fCTS = true;
  201.             hskFlags.xOn = 0x11;
  202.             hskFlags.xOff = 0x13;
  203.             hskFlags.errs = 0;
  204.             
  205.             if (gSysVersion >= 0x0700) {
  206.                 hskFlags.evts = 0;                // I can use new means of break detection. 
  207.             }
  208.             else {
  209.                 hskFlags.evts = breakEvent;        // I need the driver to post break events. 
  210.             }
  211.             
  212.             hskFlags.fInX = false;
  213.             hskFlags.fDTR = true;
  214.             
  215.             // SerHShake() does not support full DTR/CTS hardware handshaking. You   
  216.             // accomplish the same thing and more with a Control call and csCode 14. 
  217.             // You only need to specify hskFlags once, to the output driver.         
  218.             
  219.             setHskErr = Control(gOutRefNum, kSerHShakeDTR, (Ptr) &hskFlags);
  220.  
  221.             // Now reset both input and output drivers with the same configuration.  
  222.             // Only a single call to the output driver is necessary to do this.      
  223.             // Differing concurrent input/output baud rates are not supported.       
  224.             
  225.             setCfgErr = SerReset(gOutRefNum, kSerConfig);
  226.             
  227.         }
  228.         else DebugStr("\pCan't open driver");
  229. //    }
  230.     
  231.     return takeOverPort;
  232. }
  233.  
  234.  
  235.  
  236. void CloseSERD (void)
  237. {
  238.     OSErr    killErr, closeOutErr, closeInErr;
  239.  
  240.     killErr = KillIO(gInRefNum);
  241.     closeInErr = CloseDriver(gInRefNum);
  242.  
  243.     killErr = KillIO(gOutRefNum);
  244.     closeOutErr = CloseDriver(gOutRefNum);
  245.  
  246. }
  247.  
  248.         
  249.  
  250.  
  251. void CheckSerData (long /*reqBytes*/)
  252. {
  253.     OSErr    checkBufErr, serRdErr;
  254.     long    charCount;
  255.     
  256.     // long    finalTicks;
  257.     // register long overrun;
  258.     
  259.     checkBufErr = SerGetBuf(gInRefNum, &charCount);
  260.     if (checkBufErr == noErr) {
  261.     
  262.         // The general strategy here is this: if number of available characters 
  263.         // meets a certain minimum threshold, then I read in everything in the  
  264.         // buffer. If I get delayed, I'll catch up quickly.                     
  265.  
  266. //        if (charCount != 0 && charCount >= reqBytes) {
  267.         if (charCount > 0) {
  268.             
  269.             serRdErr = FSRead(gInRefNum, &charCount, &gpBitBucket[gBitBucketCount]);
  270.             if (serRdErr == noErr) {
  271.                 gBitBucketCount += charCount;
  272.                 gpBitBucket[gBitBucketCount] = 0xFF; // append an 'unfinished string' terminator
  273. //                DebugStr("\pgotsome!");
  274.             }
  275.         }
  276.     }
  277. }
  278.  
  279.  
  280.  
  281. void DoIOStuff (void)
  282. {
  283. //    DialogPtr        serMonitor;
  284. //    OSErr            primeErr;
  285. //    short            itemHit, itemType;
  286. //    ControlHandle    sendItem, stopItem, breakItem;
  287. //    Handle            spinner, flag, item;
  288. //    Rect            box;
  289. //    CntrlParam        breakPB;
  290.     
  291. //    serMonitor = GetNewDialog(rMonitor, NULL, NULL);
  292. //    SetPort((GrafPtr) serMonitor);
  293.     
  294.     // send some data
  295.     SendData();
  296.     
  297. //    PrimeTMTask();
  298.     
  299.     while(!Button())
  300.     {
  301.         EventRecord theEvent;
  302.         CheckSerData(4);
  303.         WaitNextEvent(everyEvent, &theEvent, 10, 0);
  304.         
  305.         ProcessBitBucket();
  306.         
  307. /*        if(gRcvCount >= 4)
  308.         {
  309.             gpBitBucket[5] = 0;
  310.             c2pstr(gpBitBucket);
  311.             DebugStr((unsigned char*)gpBitBucket);
  312.         }
  313. */
  314.     }
  315.         
  316. }
  317.  
  318.  
  319. void ProcessBitBucket(void)
  320. {
  321.     char* s = gpBitBucket;
  322.     short i = 0;
  323.     Boolean foundString = false;
  324.     
  325.     while(foundString == false && i < gBitBucketCount)
  326.     {
  327.         if(s[i] == '\r')
  328.         {
  329.             foundString = true;
  330.             s[i] = 0; // null terminate the string
  331.         }
  332.         if(s[i] == 0xFF)
  333.         {
  334.             foundString = false;
  335.             break;
  336.         }
  337.         
  338.         i++;
  339.     }
  340.     
  341.     if(foundString == true) 
  342.     {
  343.         c2pstr(s);
  344.         SpeakString((unsigned char*)s);
  345.         
  346.         ProcessString((unsigned char*)s);
  347.         
  348.         gBitBucketCount -= i;
  349.         BlockMove(&s[i], &s[0], gBitBucketCount);
  350.     }
  351.     
  352.     return;
  353. }
  354.  
  355. void ProcessString(unsigned char* s)
  356. {
  357.     if(EqualString(s, "\pPlay", false, false))
  358.     {
  359.         PostEvent(keyDown, ' ');
  360.     }
  361.     else if(EqualString(s, "\pPause", false, false))
  362.     {
  363.         PostEvent(keyDown, ' ');
  364.     }
  365.     else if(EqualString(s, "\pStop", false, false))
  366.     {
  367.         
  368.     }
  369.     else if(EqualString(s, "\pFast Forward", false, false))
  370.     {
  371.         
  372.     }
  373.     else if(EqualString(s, "\pRewind", false, false))
  374.     {
  375.         
  376.     }
  377.     
  378.     return;
  379. }
  380.  
  381. OSErr SendData (void)
  382. //short SendData (void)
  383. {
  384.     gpSendPB->ioCompletion = 0;//(ProcPtr)0;// SendCompRout;
  385.     gpSendPB->ioRefNum = gOutRefNum;
  386.     gpSendPB->ioBuffer = gpOutputData;
  387.     gpSendPB->ioReqCount = gOutputDataSize;
  388. //    ((ExtIOParam *) gpSendPB)->appA5 = SetCurrentA5();    // completion routine needs A5 
  389. //    gReload = true;
  390.     
  391. //    return PBWriteAsync((ParmBlkPtr) gpSendPB);            // asynchronous self-sustaining sends 
  392.     return PBWriteSync((ParmBlkPtr) gpSendPB);            // asynchronous self-sustaining sends 
  393.  
  394. }
  395.  
  396.  
  397.  
  398.  
  399.  
  400.  
  401. void CleanUp (void)
  402. {
  403.  
  404.     DisposeHandle((Handle) ghSendPB);
  405.     DisposeHandle(ghSerBuf);
  406.     DisposeHandle(ghBitBucket);
  407. }
  408.  
  409.  
  410.  
  411. #pragma mark • Commented out
  412.  
  413.  
  414. /*
  415.     while (!gAllDone) {
  416.         
  417.             CheckSerStatus();
  418.         
  419.     
  420.             if (gShouldSend && gReload) {
  421.                 gSendCount++;                // increment a counter for the output spinner 
  422.                 gReload = !gReload;
  423.             }
  424.             
  425.         // The break timer simply sets a global flag which I use to indicate when  
  426.         // to clear a break condition. Again, I use an immediate Control call, but 
  427.         // primarily for consistency, and also to show off.                        
  428.             
  429.             if (gKillBreak) {
  430.                 breakPB.ioCRefNum = gOutRefNum;
  431.                 breakPB.csCode = kSerClrBrk;
  432.                 PBControlImmed((ParmBlkPtr) &breakPB);        // SerClrBrk(), but IMMED 
  433.                 gKillBreak = !gKillBreak;
  434.                 GetDialogItem(serMonitor, kBreakButton, &itemType, &(Handle) breakItem, &box);
  435.                 HiliteControl(breakItem, kCtlEnable);
  436.             }
  437.         
  438.         // If another area of the program detects a break, I flag the occurrence here. 
  439.         
  440.             if (gBreakReceived) {
  441.                 SysBeep(1);
  442.                 SysBeep(1);
  443.                 gBreakReceived = !gBreakReceived;
  444.             }
  445.  
  446.         
  447.         // In lieu of an event loop, I just use a modal dialog with a relatively  
  448.         // simple (but unusual) filterProc. This is not a good example of how to  
  449.         // write an app. Modal dialogs are evil and to be avoided if possible.    
  450.         // Nonetheless, the filterProc is an interesting example unto itself....  
  451.     
  452.             ModalDialog(NullGrabber, &itemHit);
  453.             switch (itemHit) {
  454.                 
  455.                 case kStopButton:
  456.                     if (gShouldSend) {
  457.                         gShouldSend = !gShouldSend;
  458.                     }
  459.                     break;
  460.                 
  461.                 case kSendButton:
  462.                     if (!gShouldSend) {
  463.                         gShouldSend = !gShouldSend;
  464.                         primeErr = SendData();
  465.                     }
  466.                     break;
  467.                 
  468.                 case kBreakButton:
  469.                 
  470.                 // In another possible Mac IIfx IOP bug, SerSetBrk() called synchronously  
  471.                 // appears to hang the machine if an async write is pending. Since that is 
  472.                 // often the case (at least in this application) I work around the problem 
  473.                 // by making the Control call immediate--this prevents the hang, but also  
  474.                 // raises another interesting issue about when break is actually asserted. 
  475.                 
  476.                     breakPB.ioCRefNum = gOutRefNum;
  477.                     breakPB.csCode = kSerSetBrk;
  478.                     PBControlImmed((ParmBlkPtr) &breakPB);        // SerSetBrk(), but IMMED 
  479.                     
  480.                 // With break asserted, I prime a Time Manager task to flag the end of    
  481.                 // the break, and disable the Break button so that it cannot be selected  
  482.                 // again until break is negated.                                          
  483.                 
  484.                     PrimeTime((QElemPtr) &gUnBreakTask, kBreakLength);
  485.                     GetDialogItem(serMonitor, kBreakButton, &itemType, &(Handle) breakItem, &box);
  486.                     HiliteControl(breakItem, kCtlDisable);
  487.                     break;
  488.                 
  489.                 case kQuitButton:
  490.                     gAllDone = true;
  491.                     break;
  492.                 
  493.             }
  494.     }
  495.     */
  496.     
  497. //    DisposeDialog(serMonitor);
  498.  
  499.  
  500. /*
  501. pascal void FlagBreakTimeout (void)
  502. {
  503.     long    oldA5;
  504.     
  505.     oldA5 = SetA5(((ExtTMTask *) GetTMTaskPtr())->appA5);        // retrieve A5 
  506.     
  507.     gKillBreak = true;
  508.     
  509.     SetA5(oldA5);
  510.     
  511. }
  512. */
  513.  
  514.  
  515. /*
  516. pascal Boolean NullGrabber (DialogPtr dPtr, EventRecord *evt, short *itemHit)
  517. {
  518.     EventRecord    driverEvent;
  519.     
  520.     // Without this filterProc, none of the animation works: 
  521.     
  522.     // In order to keep things rolling along even when there are no events    
  523.     // such as mouse clicks or keystrokes, I have to return true in response  
  524.     // to null events. This is unusual, but otherwise ModalDialog() "handles" 
  525.     // null events by eating them and waiting for something better. This is   
  526.     // bad if I need to turn the spinner or clear a break condition.          
  527.     
  528.     // Also, I check for driver events here in order to detect breaks. 
  529.     
  530.     if (GetNextEvent(driverMask, &driverEvent)) {
  531.         if ((*(SERDEventMessage *) &driverEvent.message).drvrPosting == gInRefNum) {
  532.             gBreakReceived = ((*(SERDEventMessage *) &driverEvent.message).readR0 & breakR0) != 0;
  533.         }
  534.     }
  535.     
  536.     if (evt->what == nullEvent) {
  537.         *itemHit = 0;
  538.         return true;
  539.     }
  540.     
  541.     else {
  542.         return false;
  543.     }
  544.     
  545. }*/
  546.  
  547. /*
  548.  
  549. pascal void SendCompRout (void)
  550. {
  551.     long    oldA5;
  552.     
  553.     oldA5 = SetA5(((ExtIOParam *) GetParmBlkPtr())->appA5);        // retrieve A5 
  554.     
  555.     if (gShouldSend && !gAllDone) {
  556.         gReload = true;
  557.         gpSendPB->ioCompletion = (ProcPtr) SendCompRout;
  558.         PBWriteAsync((ParmBlkPtr) gpSendPB);            // this is the self-sustaining part     
  559.     }
  560.     
  561.     SetA5(oldA5);
  562.     
  563. }
  564. */
  565.  
  566. /*
  567. OSErr AssertDrvrOpen (Str255 name, short *refNum)
  568. {
  569.     DCtlHandle    *pUTEntry;
  570.     Ptr            pDrvr;
  571.     OSErr        result = notOpenErr;        // assume not open 
  572.     short        unitNo;
  573.     char        *aDrvrName;
  574.     
  575.     // The point here is to determine whether a driver is open, given its name.  
  576.     // This allows one to check a driver to see if it's open without hard coding 
  577.     // its reference number. (Normally, the way to get the refNum is to open     
  578.     // the driver--but that defeats the whole purpose!)                          
  579.     // This is an extension of the code discussed in Tech Note #71.              
  580.     
  581.     *refNum = 0;
  582.     pUTEntry = *(DCtlHandle **) UTableBase;
  583.     for (unitNo = 0; unitNo < *(short *) UnitNtryCnt; unitNo++, pUTEntry++) {
  584.         if (*pUTEntry != nil && **pUTEntry != nil) {
  585.             if (((***pUTEntry).dCtlFlags & 1 << dRAMBased) != 0)
  586.                 pDrvr = *(Handle) (***pUTEntry).dCtlDriver;
  587.             else
  588.                 pDrvr = (***pUTEntry).dCtlDriver;
  589.             
  590.             if (pDrvr != nil) {
  591.                 aDrvrName = pDrvr + drvrName;
  592.                 if (memcmp(aDrvrName, name, 1 + name[0]) == 0) {
  593.                     // We found the one we're after. 
  594.                     *refNum = ~unitNo;
  595.                     if (((***pUTEntry).dCtlFlags & 1 << dOpened) != 0)
  596.                         result = noErr;
  597.                     break;
  598.                 }
  599.             }
  600.         }
  601.     }    
  602.     
  603.     return result;
  604. }
  605.  
  606. */
  607.  
  608.  
  609. /*
  610. void CheckSerStatus (void)
  611. {
  612.     OSErr        checkStatErr, panicErr;
  613.     IOParam        altSendPB, *pAltSendPB = &altSendPB;
  614.     CntrlParam    statPB;
  615.     SerStaRec    serStat;
  616.     
  617.     statPB.ioCRefNum = gInRefNum;
  618.     statPB.csCode = kSerStatus;
  619.     checkStatErr = PBStatusImmed((ParmBlkPtr) &statPB);
  620.     serStat =  *(SerStaRec *) &statPB.csParam;
  621.     
  622.     // I check to see if the remote system has told me to stop sending. 
  623.     
  624.     gHeldOff = (Boolean) serStat.ctsHold;
  625.     
  626.     // Check for errors. 
  627.     
  628.     if (serStat.cumErrs & swOverrunErr)
  629.         DebugStr("\pSoftware Overrun Error");
  630.     else if (serStat.cumErrs & parityErr)
  631.         DebugStr("\pParity Error");
  632.     else if (serStat.cumErrs & hwOverrunErr)
  633.         DebugStr("\pHardware Overrun Error");
  634.     else if (serStat.cumErrs & framingErr)
  635.         DebugStr("\pFraming Error");
  636.     
  637.     // If I have System 7.0 or better, I can check directly to see if   
  638.     // I've received a break. Usually I don't check for a feature this  
  639.     // way, but in this case I have no alternative.                     
  640.     
  641.     if (gSysVersion >= 0x0700) {
  642.         gBreakReceived = (serStat.cumErrs & breakErr) != 0;
  643.     }
  644.     
  645.     // All I do here is send a small "panic" packet of characters back  
  646.     // to the remote system when it fills _my_ buffer. I don't actually 
  647.     // know the exact state of my buffer, but I can see if I've told    
  648.     // the remote system to shut up, indicating that I'm mostly full.   
  649.     
  650.     if (checkStatErr == noErr) {
  651.         if (( (unsigned char) serStat.xOffSent & dtrNegated) != 0) {
  652.             pAltSendPB->ioCompletion = NULL;
  653.             pAltSendPB->ioRefNum = gOutRefNum;
  654.             pAltSendPB->ioBuffer = panicString;
  655.             pAltSendPB->ioReqCount = strlen(panicString);
  656.             PBWriteAsync((ParmBlkPtr) pAltSendPB);
  657.             
  658.             // The program may hang here if the user quits the    
  659.             // remote application first--that could hold off our  
  660.             // serial output, leaving a pending asynchronous I/O  
  661.             // request and keeping us in an infinite loop.        
  662.             
  663.             while (pAltSendPB->ioResult > 0) {}                // I'll fix it later. 
  664.             // The reason I do this instead of just calling it synchronously 
  665.             // is so that if I do hang, I'll hang in my code for an obvious  
  666.             // reason instead of hanging up in the Device Manager.           
  667.             panicErr = pAltSendPB->ioResult;
  668.         }
  669.     }
  670.     
  671.     
  672. }
  673. */